﻿using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;

namespace IMDBServiceAPI
{
    /// <summary>
    /// Class that provides API to retrieve movie information
    /// from IMDB website
    /// </summary>
    public class MovieInfo
    {
        #region Properties

        /// <summary>
        /// Gets the title of the movie
        /// </summary>
        public string Title { get; private set; }
        /// <summary>
        /// Gets the IMDB url of the movie
        /// </summary>
        public string ImdbUrl { get; private set; }
        /// <summary>
        /// Gets the country where the movie was produced
        /// </summary>
        public string Country { get; private set; }
        /// <summary>
        /// Gets the languages used in the movie delimited by ;
        /// </summary>
        public string Languages { get; private set; }
        /// <summary>
        /// Gets the genres of the movie delimited by ;
        /// </summary>
        public string Genres { get; private set; }
        /// <summary>
        /// Gets the rating of the movie on a scale of 10 
        /// </summary>
        public float Rating { get; private set; }
        /// <summary>
        /// Gets the year in which the movie was released
        /// </summary>
        public int Year { get; private set; }

        #endregion

        // Constructor has been made private
        // to use the Factory pattern
        private MovieInfo() { }

        #region Public methods

        /// <summary>
        /// Retrieves the instance of MovieInfo containing the metadata of the movie requested
        /// </summary>
        /// <param name="strMovieTitle">Title of the movie</param>
        /// <returns>Instance of MovieInfo containing metadata of the movie</returns>
        /// <exception cref="RetrievalFailureException" />
        public static MovieInfo GetMovieInfo(string strMovieTitle)
        {
            // Retrieves the imdb url of the movie
            string strUrl = RetrieveIMDBMovieUrl(strMovieTitle);

            // In case the url is not found
            if (String.IsNullOrEmpty(strUrl))
                // throws an exception notifying the absence of the movie information
                throw new RetrievalFailureException(strMovieTitle);

            // retreives metada and returns the instance
            return RetrieveMovieInfo(strUrl);
        }

        /// <summary>
        /// Converts the value of the current IMDBServiceAPI.MovieInfo
        /// to its equivalent string representation
        /// </summary>
        /// <returns>String representation of the current IMDBServiceAPI.MovieInfo instance</returns>
        public override string ToString()
        {
            // Overridden for testing the information retrieved
            return String.Format("{0} ({1}) - {2}\r\n{3}\r\n{4}",
                Title,
                Year,
                Rating,
                ImdbUrl,
                Genres);
            
            // return base.ToString();    
        }

        #endregion

        #region Private methods

        // Retrieves the IMDB url of the movie title
        private static string RetrieveIMDBMovieUrl(string strMovieTitle)
        {
            string movieUrl = string.Empty;

            // Creates a request to IMDB.com passing the movie title as parameter
            // http://www.imdb.com/find/?s=tt&q=<movietitle>
            // This url will search for the movie in the database
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
                String.Format(
                    IMDBServiceConstants.URL_IMDB_FIND,
                    strMovieTitle.Replace(' ', '+')));
            request.Timeout = IMDBServiceConstants.URL_TIME_OUT;    // a request time out of 30 seconds is provided
            
            HttpWebResponse response = default(HttpWebResponse);
            string responseContent = string.Empty;
            try
            {
                // Retrieves the response from the website
                response = (HttpWebResponse)request.GetResponse();

                // IMDB often provides the list of links matching the movie title.
                // In such cases, url of one of the matches has to be picked
                if (response.ResponseUri.AbsoluteUri.Contains(IMDBServiceConstants.IMDB_URL_FIND))
                {
                    // Reads in the response from the web site.
                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                    {
                        responseContent = reader.ReadToEnd();
                        /* For debugging */
                        //File.WriteAllText(
                        //    Path.Combine(
                        //        Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                        //        String.Format("{0}.txt", strMovieTitle)),
                        //    responseContent);
                    }

                    // Reads through the response, and extracts imdb url
                    // based on an algorithm
                    movieUrl = ExtractMovieUrl(responseContent);

                }
                // On other case, IMDB directly redirects to the url of the movie
                // In such cases, the url is directly retrieved in the response
                else
                {
                    // forms the imdb url of the movie title and returns it
                    movieUrl = String.Concat(
                        response.ResponseUri.Scheme,
                        Uri.SchemeDelimiter,
                        response.ResponseUri.Host,
                        response.ResponseUri.AbsolutePath);
                    
                }
            }
            // In case of a time out
            catch (WebException) { }
            finally { request = null; response = null; }

            // returns the IMDB url;
            return movieUrl; 
        }

        // Reads the response, and extracts the imdb url of a matching title
        private static string ExtractMovieUrl(string strWebResponse)
        {
            string imdbUrl = string.Empty;
            int loopCase = default(int);
            string urlExpression = string.Empty;
            RegexOptions options = default(RegexOptions);
            /*****************************************
             * IMDB provides four features 
             *    1) Media From (direct match)
             *    2) Titles (Exact matches)
             *    3) Titles (Partial matches)
             *    4) Popular Titles
             * The algorithm below, fetches the url based on precedence order
             * mentioned above.
             * // TODO : This algorithm is crude and might not fetch
             * //         accurate results
             *****************************************/
            do
            {
                switch (loopCase)
                {
                        // retrieves url from Media from
                    case 0:
                        urlExpression = IMDBServiceConstants.EXPR_MOVIE_URL_EXTRACT_MEDIA;
                        break;
                        // retrieves url from Popular Titles
                    case 1:
                        urlExpression = IMDBServiceConstants.EXPR_MOVIE_URL_EXTRACT_POPULAR;
                        break;
                        // retrieves url from Exact Matches
                    case 2:
                        urlExpression = IMDBServiceConstants.EXPR_MOVIE_URL_EXTRACT_EXACT;
                        break;
                        // retrieves url from Partial Matches
                    case 3:
                        urlExpression = IMDBServiceConstants.EXPR_MOVIE_URL_EXTRACT_PARTIAL;
                        break;
                }

                // Retrieves the url
                imdbUrl = Regex.Match(
                    Regex.Match(strWebResponse, urlExpression)
                        .Groups[IMDBServiceConstants.EXPR_GROUP_AREA].Value,
                    IMDBServiceConstants.EXPR_MOVIE_URL_EXTRACT_AREA)
                        .Groups[IMDBServiceConstants.EXPR_GROUP_URL].Value;

                // Loops until a url is found
                loopCase++;                
            } while (String.IsNullOrEmpty(imdbUrl) && loopCase < 4);

            // If url was not found even after looping (unlikely to happen)
            // returns an empty url.
            imdbUrl = String.IsNullOrEmpty(imdbUrl) ?
                String.Empty :
                String.Format(
                    IMDBServiceConstants.URL_IMDB_TITLE,
                    imdbUrl);

            return imdbUrl;
        }

        // Retrieves the metadata of the movie
        private static MovieInfo RetrieveMovieInfo(string strImdbUrl)
        {
            // Makes a request to IMDB url of the movie
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(strImdbUrl);
            request.Timeout = IMDBServiceConstants.URL_TIME_OUT;

            MovieInfo info = default(MovieInfo);
            HttpWebResponse response = default(HttpWebResponse);
            string strResponse = string.Empty;
            try
            {
                // Reads the response
                response = (HttpWebResponse)request.GetResponse();
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    strResponse = reader.ReadToEnd();
                    /* For debugging */
                    //File.WriteAllText(
                    //    Path.Combine(
                    //        Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
                    //        String.Format("{0}_1.txt", strImdbUrl.Split('/')[strImdbUrl.Split('/').Length - 2])),
                    //    strResponse);

                }

                // Parses the response to instance of MovieInfo
                info = ParseMovieInfo(strResponse, strImdbUrl);
            }
            catch (WebException) { }
            finally { request = null; response = null; }

            // returns the instance
            return info;
        }

        // Reads the response and parses the metadata into instance of MovieInfo
        private static MovieInfo ParseMovieInfo(string strResponse, string strImdbUrl)
        {
            // Creates an instance of MovieInfo
            MovieInfo movieInfo = new MovieInfo();
            // Fills IMDB url property
            movieInfo.ImdbUrl = strImdbUrl;
            // Fills Title property
            movieInfo.Title =
                Regex.Match(
                    strResponse,
                    IMDBServiceConstants.EXPR_MOVIE_TITLE, RegexOptions.Multiline).Groups[
                    IMDBServiceConstants.EXPR_GROUP_TITLE].Value;
            // Fills Year property
            movieInfo.Year =
                int.Parse(
                    Regex.Match(
                        strResponse,
                        IMDBServiceConstants.EXPR_MOVIE_TITLE).Groups[
                        IMDBServiceConstants.EXPR_GROUP_YEAR].Value);
            // Fills Rating property
            movieInfo.Rating =
                float.Parse(
                    Regex.Match(
                        strResponse,
                        IMDBServiceConstants.EXPR_MOVIE_RATING).Groups[
                        IMDBServiceConstants.EXPR_GROUP_RATING].Value);

            // Fills Genre property
            StringBuilder sBuilder = new StringBuilder();
            string strGenreArea = Regex.Match(
                strResponse, IMDBServiceConstants.EXPR_MOVIE_GENRE_LIST)
                .Groups[IMDBServiceConstants.EXPR_GROUP_AREA].Value;
            foreach (Match match in
                Regex.Matches(
                    strGenreArea,
                    IMDBServiceConstants.EXPR_MOVIE_GENRE))
            {
                sBuilder.AppendFormat(IMDBServiceConstants.FORMAT_GENRE_LIST,
                    match.Groups[IMDBServiceConstants.EXPR_GROUP_GENRE].Value);
            }
            sBuilder.Remove(sBuilder.Length - 1, 1);
            movieInfo.Genres = sBuilder.ToString();

            // TODO : Fill the rest of the property

            // returns the instance
            return movieInfo;
        }

        #endregion
    }
}
